# _*_coding:utf-8_*_
#shenxiao

import torch
import torch.nn.functional as F
#transformer之中用了 scaled dot-product attention
    #3.2.1
#dot-product attention 基于相似度的乘积注意力
#首先，什么是注意力机制呢？
'''
对于一个序列 算出一个新的表征
在注意力机制中 是对这个序列进行加权求和 从而算出的表征

对于全连接来说 也是进行表征
nn.Linear(2,3) 可以理解为 将一个序列 从2维度变到3维 怎么变？ 加权求和
而映射的权重既不是归一化的 也不是attention算出来的 而是随机初始化的 一般来说符合高斯分布的
但是在后面的反向传播 梯度更新中 会让权重进行对应的调整 这个调整幅度也太大了吧 太难了吧 而且每次最有不一定是结果最好啊 这是不是类似贪心算法呢？

attention中映射的权重是算出来的 而且是经过 softmax进行归一化处理后得到的
怎么算的？通过Q 和  K 的相似度 （也就是点积） 再除以特征维度的根号值得到的

#相似度的直观解释：也就是一个单词对一整个序列 也就是五个单词 （在这里） 的相似度
#每个单词都算一下这种相似度 构成一个5*5的相似度矩阵 这就是 pe_embadding

softmax同时也是单调的 原本存在的相似度排序 softmax之后并不会改变
同时softmax不是线性放大的 而是逐渐放大 会导致分布的不同 会让分布更加尖锐？
softmax会让大的值更大 小的值更小 整体分布是会变得比原来更加尖锐的

所以我们要除以一个特征维度的根号
本质上是我们希望softmax之后出来的不同概率的方差不要那么大 再往深层挖 就是希望雅可比矩阵的导数不为0
总之 除以根号下特征维度数就是scale操作 起的作用是让得出的诸多概率的分布 不要那么尖锐 同时可以增大雅可比矩阵的值
'''#shenxiao

#下面做实验，来演示一下上述说法。
# softmax演示 scaled 的重要性
# 以概率的角度演示
score = torch.randn(5)
print(score)
# tensor([ 1.5219,  0.3743, -0.2762,  0.4787, -0.0251])
# 随机初始化一个序列 作为query 和 key点积之后的值 query代表的是一个词 而key则是整个序列 算出来的值就是 query和key的相似度
alpha1 = 0.1
alpha2 = 10
#分数乘以一个比较小的数 也就是相当于 除以特征维度的根号
prob1 = F.softmax(score*alpha1 , -1)    #在-1维进行softmax是什么意思呢？
#分数乘以一个比较大的数 扩大效果
prob2 = F.softmax(score*alpha2 , -1)

# print(prob1)
# print(prob2)
# tensor([0.1885, 0.2283, 0.1908, 0.1908, 0.2016])
# tensor([4.7607e-09, 1.0000e+00, 1.6487e-08, 1.6324e-08, 4.1115e-06])
#可以很明显的看出 scaled之后的诸多概率相差不大 同时方差比较小 数值比较稳定 (对应来说就是分布比较平滑 平稳)
#而不经过scaled之后的诸多概率方差比较大 数值比较小 有的甚至直接超出计数范围 相当于0 分布也比较尖锐 这样就很不好

#以雅可比矩阵的角度进行阐释
    #在这里雅可比矩阵也就相当于 反向传播中某一步的梯度
#雅可比矩阵的用法事例
def exp_reducer(x):
    return x.exp().sum(dim=1)
inputs = torch.rand(2,2)
# print(inputs)
A = torch.autograd.functional.jacobian(exp_reducer,inputs)
# print(A)

# tensor([[0.4735, 0.0204],
#         [0.0440, 0.2358]])
# tensor([[[1.6057, 1.0206],
#          [0.0000, 0.0000]],
#
#         [[0.0000, 0.0000],
#          [1.0449, 1.2660]]])

#具体实现
def softmax_func(score):
    return F.softmax(score)
jaco_ma1 = torch.autograd.functional.jacobian(softmax_func,score*alpha1)
jaco_ma2 = torch.autograd.functional.jacobian(softmax_func, score*alpha2)
print(jaco_ma1)
print(jaco_ma2)
# tensor([[ 0.1613, -0.0335, -0.0443, -0.0412, -0.0423],
#         [-0.0335,  0.1381, -0.0363, -0.0337, -0.0346],
#         [-0.0443, -0.0363,  0.1712, -0.0447, -0.0459],
#         [-0.0412, -0.0337, -0.0447,  0.1622, -0.0426],
#         [-0.0423, -0.0346, -0.0459, -0.0426,  0.1655]])
# tensor([[ 2.7678e-04, -1.6484e-16, -2.7413e-04, -1.6573e-07, -2.4817e-06],
#         [-1.6484e-16,  5.9539e-13, -5.8953e-13, -3.5641e-16, -5.3369e-15],
#         [-2.7413e-04, -5.8953e-13,  9.7424e-03, -5.9273e-04, -8.8755e-03],
#         [-1.6573e-07, -3.5641e-16, -5.9273e-04,  5.9826e-04, -5.3658e-06],
#         [-2.4817e-06, -5.3369e-15, -8.8755e-03, -5.3658e-06,  8.8833e-03]])

#可以很明显的看出 经过scaled的雅可比矩阵的数值比较平稳 比较大
#而不scaled的雅可比矩阵 方差明显变大 数值变小 分布十分尖锐
        #因为其数值比较小 所以其还会造成梯度消失的问题 导致训练失败

###综上所述，scaled的作用可以在经过softmax之后 让数据数值变得平稳 方差变小 分布平稳 同时还能防止数值过小导致的梯度小消失的问题
#shenxiao 2022-04-05
